#ifndef _GOVT_NETWORK_HPP_
#define _GOVT_NETWORK_HPP_
/*******************************************************************************
 ** Government Sanctioned Espionage RPG Server                                **
 ** http://www.government-sanctioned.us/                                      **
 **===========================================================================**
 ** Name: network.hpp                                                       **
 ** Description:              **
 **                                                                           **
 ** Open Source Initiative (OSI) Approved License                             **
 **                                                                           **
 ** The contents of this file are subject to the terms of the                 **
 ** Common Development and Distribution License, Version 1.0 only             **
 ** (the "License").  You may not use this file except in compliance          **
 ** with the License.                                                         **
 **                                                                           **
 ** You can find a copy of the license in the LICENSE file within             **
 ** this distribution or at $GOVTSANCT_WIKI/License-software.                **
 ** Software distributed under the License is distributed on an "AS IS"       **
 ** basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.           **
 ** See the License for the specific language governing permissions           **
 ** and limitations under the License.                                        **
 **                                                                           **
 ** When distributing Covered Code, include this CDDL header in each          **
 ** file and include the License file at $HOME/LICENSE.                      **
 ** If applicable, add the following below this header, with the indicated    **
 ** fields enclosed by brackets "[]" replaced with your own identifying       **
 ** information: Portions Copyright [yyyy] [name of copyright owner]          **
 **                                                                           **
 ** Copyright (c) 2009-2011  Barry Gian James <gian@gamingods.net>            **
 ** All rights reserved.                                                      **
 **                                                                           **
 ** Ref: $HeadURL$
 ******************************************************************************/

// $Id$
// Modified $Date$ by $Author$

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <set>
#include <deque>
#include <vector>
#include <string>
#include "account.hpp"

#define DEFAULT_BCAST_PORT	23230
#define DEFAULT_TEST_PORT	23231
#define	DEFAULT_MUD_PORT	23232


//! @class subscriber
//! @brief default base class for all client subscribers.
class subscriber
{
public:
	typedef boost::shared_ptr<subscriber> pointer;

	virtual ~subscriber() { }
	virtual void deliver(const std::string &msg) = 0;
};

#define CHANNEL_FACTION 0x0001
#define CHANNEL_PASSWD	0x0002
#define CHANNEL_INVIS	0x0004
#define CHANNEL_PRIVATE	0x0008
#define CHANNEL_TEAM	0x0010
#define CHANNEL_VIEW	0x0020

#define MAX_RECV_LEN	256

// Disconnection codes
#define DISCO_LOGOUT	1	// normal logout/disconnection
#define DISCO_IDLE		2	// disconnected for being idle
#define DISCO_KILLED	3	// forceably removed with @kill
#define DISCO_REBOOT	4	// system going down for reboot
#define DISCO_BADLOGIN	5	// too many failed login attempts
#define DISCO_CLOSED	6	// no new or otherwise users allowed in
#define DISCO_FULL		7	// too many players logged in

#define CLIENT_MXP_ENA		0x0001
#define CLIENT_QUOTA_ENA	0x0002
#define CLIENT_I3_ENA		0x0004
#define CLIENT_IPV6_ENA		0x0008
#define CLIENT_COLOR_ENA	0x0010
#define CLIENT_COMPRESSION_ENA 0x0020
#define CLIENT_SUSPECT		0x0040

#define DEFAULT_OUTPUT_WAIT	15


//! @class channel
//! @brief Describes network channels available and for each connection.
class channel
{
public:
	SMART_PTR_DECL(channel);

	channel() { _flags = 0; }

	void join(subscriber::pointer sub) { _subscribers.insert(sub); }
	void leave(subscriber::pointer sub) { _subscribers.erase(sub); }
	void deliver(const std::string & msg)
	{
		std::for_each(_subscribers.begin(), _subscribers.end(),
				boost::bind(&subscriber::deliver, _1, boost::ref(msg)));
	}
	unsigned int		Flags() const { return _flags; }
	void				Flags(unsigned int f) { _flags |= f; }
	void				ZeroFlags() { _flags = 0x0000; }

	channel &	operator = (const channel & c) {
		_subscribers = c._subscribers;
		_flags = c._flags;

		return *this;
	}

private:
	std::set<subscriber::pointer> 	_subscribers;
	unsigned int				_flags;
};

extern channel::pointer	DefaultChannel;
extern channel::pointer	NewbieChannel;
extern channel::pointer	EDDChannel;

//! @class telnet_connection
//! @brief Smart pointer enabled handler for telnet-type connections
class telnet_connection :
	public subscriber,
	public boost::enable_shared_from_this<telnet_connection>
{
public:
	typedef boost::shared_ptr<telnet_connection> pointer;

	telnet_connection(boost::asio::io_service & ios, channel::pointer & ch = DefaultChannel)
		: _channel(ch), _sock(ios), _input_deadline(ios),
		  _non_empty_output_queue(ios), _output_deadline(ios)
	{
		_input_deadline.expires_at(boost::posix_time::pos_infin);
		_output_deadline.expires_at(boost::posix_time::pos_infin);
		_non_empty_output_queue.expires_at(boost::posix_time::pos_infin);
		_login = boost::posix_time::from_time_t(std::time(NULL));
		_flags = (CLIENT_I3_ENA|CLIENT_COLOR_ENA|CLIENT_COMPRESSION_ENA);
	}

	static pointer create(boost::asio::io_service & ios)
	{
		return pointer(new telnet_connection(ios));
	}

	boost::asio::ip::tcp::socket & socket() { return _sock; }

	void start()
	{
		// TODO What's the first thing we write to the client? Get compression?
		_channel->join(shared_from_this());
		start_read();
		_input_deadline.async_wait(boost::bind(&telnet_connection::check_deadline,
				shared_from_this(), &_input_deadline));

		await_output();

		_output_deadline.async_wait(boost::bind(&telnet_connection::check_deadline,
				shared_from_this(), &_output_deadline));
	}

	uint16_t		Flags() const { return _flags; }
	void			Flags(uint16_t f) { _flags |= f; }
	void			ZeroFlag(uint16_t f) { _flags &= ~f; }
	void			ZeroFlags() { _flags = 0; }

    int		IsMXPEnabled() { return (_flags & CLIENT_MXP_ENA); }
    int		IsQuotaEnabled() { return (_flags & CLIENT_QUOTA_ENA); }
    int		IsI3Enabled() { return (_flags & CLIENT_I3_ENA); }
    int		IsIPv6Enabled() { return (_flags & CLIENT_IPV6_ENA); }
    int		IsCompressionEnabled() { return (_flags & CLIENT_COMPRESSION_ENA); }
    int		IsThemeable() { return (_flags & CLIENT_COLOR_ENA); }


private:
	void stop()
	{
		_channel->leave(shared_from_this());
		_sock.close();
		_input_deadline.cancel();
		_non_empty_output_queue.cancel();
		_output_deadline.cancel();
	}

	bool stopped() const { return !_sock.is_open(); }

	// pure virtual handler
	void deliver(const std::string & msg)
	{
		_output_queue.push_back(msg + "\r\n");
	}

	void start_read()
	{
		_input_deadline.expires_from_now(boost::posix_time::seconds(15));
		boost::asio::async_read_until(_sock,_data, '\n',
				boost::bind(&telnet_connection::handle_read, shared_from_this(), _1));
		_lastrcv = boost::posix_time::from_time_t(time(NULL));
	}

	void handle_read(const boost::system::error_code & ec)
	{
		if (stopped()) return;
		if (!ec)
		{
			std::string msg;
			std::istream is(&_data);
			std::getline(is,msg);
			if (!msg.empty())
				_channel->deliver(msg);
			else
				if (_output_queue.empty())
				{
					_output_queue.push_back("\n");
					_non_empty_output_queue.expires_at(boost::posix_time::neg_infin);
				}
			start_read();
		}
		else
			stop();
	}

	void await_output()
	{
		if (stopped()) return;
		if (_output_queue.empty())
		{
			_non_empty_output_queue.expires_at(boost::posix_time::pos_infin);
			_non_empty_output_queue.async_wait(boost::bind(&telnet_connection::await_output, shared_from_this()));
		}
		else
			start_write();
	}

	void start_write()
	{
		_output_deadline.expires_from_now(boost::posix_time::seconds(DEFAULT_OUTPUT_WAIT));
		boost::asio::async_write(_sock, boost::asio::buffer(_output_queue.front()),
				boost::bind(&telnet_connection::handle_write, shared_from_this(), _1));
//		_lastrcv = boost::posix_time::from_time_t(time(NULL));
	}

	void handle_write(const boost::system::error_code & ec)
	{
		if (stopped()) return;
		if (!ec)
		{
			_output_queue.pop_front();
			await_output();
		} else
			stop();
	}

	void check_deadline(boost::asio::deadline_timer * dl)
	{
		if (stopped()) return;
		if (dl->expires_at() <= boost::asio::deadline_timer::traits_type::now())
			stop();
		else
			dl->async_wait(boost::bind(&telnet_connection::check_deadline,
					shared_from_this(), dl));
	}

	channel::pointer				_channel;
	boost::asio::ip::tcp::socket	_sock;
	boost::asio::streambuf			_data;
	boost::asio::deadline_timer		_input_deadline, _output_deadline;
	boost::asio::deadline_timer		_non_empty_output_queue;
	// For FIFO-like buffering, can replace below with: boost::circular_buffer_space_optimized
	std::deque<std::string>			_output_queue;
	boost::posix_time::ptime		_lastrcv, _login;
	uint16_t						_flags;
	Account::pointer				_account;

};

class tcp_broadcast : public subscriber
{
public:
	tcp_broadcast(boost::asio::io_service & ios, const boost::asio::ip::tcp::endpoint & bcast)
	: _sock(ios)
	{
		_sock.connect(bcast);
	}

	boost::asio::ip::tcp::socket & subscribe() { return _sock; }

private:
	void	deliver(const std::string & msg)
	{
		boost::system::error_code ignored_ec;
		_sock.send(boost::asio::buffer(msg), 0, ignored_ec);
	}
	boost::asio::ip::tcp::socket _sock;
};

//! @class mud_network
//! @brief Implements the TCP/IP binding and socket interface
class mud_network
{
public:
	mud_network(boost::asio::io_service & ios, const boost::asio::ip::tcp::endpoint & listen,
			const boost::asio::ip::tcp::endpoint bcast)
		: _ios(ios), _acceptor(ios, listen)
	{
		subscriber::pointer	nc(new tcp_broadcast(_ios, bcast));
		_channels["default"]->join(nc);

		telnet_connection::pointer new_session(new telnet_connection(_ios));

		start_accept();
	}

private:

	void start_accept()
	{
		telnet_connection::pointer new_connection =
				telnet_connection::create(_acceptor.get_io_service());

		_acceptor.async_accept(new_connection->socket(),
				boost::bind(&mud_network::handle_accept, this, new_connection,
						boost::asio::placeholders::error));
	}

	void handle_accept(telnet_connection::pointer new_connection,
			const boost::system::error_code & ec)
	{
		if (!ec)
			new_connection->start();

		start_accept();
	}

	void	init_channels()
	{
		_channels["default"] = 	DefaultChannel;
		_channels["newbie"] =	NewbieChannel;
		_channels["edd"] = 		EDDChannel;
	}

	void	wallops(std::string & msg)
	{
		_channels["default"]->deliver("Game Control>> " + msg);
	}

	boost::asio::ip::tcp::acceptor	_acceptor;
	boost::asio::io_service &		_ios;
	std::map<std::string, channel::pointer>	_channels;
};


#endif /* _GOVT_NETWORK_HPP_ */
